/*
 * File      : serial.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2006-03-13     bernard      first version
 * 2012-05-15     lgnq         modified according bernard's implementation.
 * 2012-05-28     bernard      code cleanup
 */

#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>

rt_inline void serial_ringbuffer_init(struct serial_ringbuffer* rbuffer)
{
	rt_memset(rbuffer->buffer, 0, sizeof(rbuffer->buffer));
	rbuffer->put_index = 0;
	rbuffer->get_index = 0;
}

rt_inline void serial_ringbuffer_putc(struct serial_ringbuffer* rbuffer, char ch)
{
	rt_base_t level;

	/* disable interrupt */
	level = rt_hw_interrupt_disable();

	rbuffer->buffer[rbuffer->put_index] = ch;
	rbuffer->put_index = (rbuffer->put_index + 1) & (SERIAL_RBUFFER_SIZE - 1);

	/* if the next position is read index, discard this 'read char' */
	if (rbuffer->put_index == rbuffer->get_index)
	{
		rbuffer->get_index = (rbuffer->get_index + 1) & (SERIAL_RBUFFER_SIZE - 1);
	}

	/* enable interrupt */
	rt_hw_interrupt_enable(level);
}

rt_inline int serial_ringbuffer_putchar(struct serial_ringbuffer* rbuffer, char ch)
{
	rt_base_t level;
	rt_uint16_t next_index;

	/* disable interrupt */
	level = rt_hw_interrupt_disable();

	next_index = (rbuffer->put_index + 1) & (SERIAL_RBUFFER_SIZE - 1);
	if (next_index != rbuffer->get_index)
	{
		rbuffer->buffer[rbuffer->put_index] = ch;
		rbuffer->put_index = next_index;
	}
	else
	{
		/* enable interrupt */
		rt_hw_interrupt_enable(level);
		return -1;
	}

	/* enable interrupt */
	rt_hw_interrupt_enable(level);
	return 1;
}

rt_inline int serial_ringbuffer_getc(struct serial_ringbuffer* rbuffer)
{
	int ch;
	rt_base_t level;

	ch = -1;
	/* disable interrupt */
	level = rt_hw_interrupt_disable();
	if (rbuffer->get_index != rbuffer->put_index)
	{
		ch = rbuffer->buffer[rbuffer->get_index];
		rbuffer->get_index = (rbuffer->get_index + 1) & (SERIAL_RBUFFER_SIZE - 1);
	}
	/* enable interrupt */
	rt_hw_interrupt_enable(level);
	return ch;
}

rt_inline rt_uint32_t serial_ringbuffer_size(struct serial_ringbuffer* rbuffer)
{
	rt_uint32_t size;
	rt_base_t level;

	level = rt_hw_interrupt_disable();
	size = (rbuffer->put_index - rbuffer->get_index) & (SERIAL_RBUFFER_SIZE - 1);
	rt_hw_interrupt_enable(level);

	return size;
}

/* RT-Thread Device Interface */

/*
 * This function initializes serial
 */
static rt_err_t rt_serial_init(struct rt_device *dev)
{
	rt_err_t result = RT_EOK;
	struct rt_serial_device *serial;

	RT_ASSERT(dev != RT_NULL);
	serial = (struct rt_serial_device*) dev;

	if (!(dev->flag & RT_DEVICE_FLAG_ACTIVATED))
	{
		/* apply configuration */
		if (serial->ops->configure)
			result = serial->ops->configure(serial, &serial->config);

		if (result != RT_EOK)
			return result;

		if (dev->flag & RT_DEVICE_FLAG_INT_RX)
			serial_ringbuffer_init(serial->int_rx);

		if (dev->flag & RT_DEVICE_FLAG_INT_TX)
			serial_ringbuffer_init(serial->int_tx);		 

		/* set activated */
		dev->flag |= RT_DEVICE_FLAG_ACTIVATED;
	}

	return result;
}

static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag)
{
	struct rt_serial_device *serial;
	rt_uint32_t int_flags = 0;

	RT_ASSERT(dev != RT_NULL);
	serial = (struct rt_serial_device*) dev;

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
		int_flags = RT_SERIAL_RX_INT;
	if (dev->flag & RT_DEVICE_FLAG_INT_TX)
		int_flags |= RT_SERIAL_TX_INT;

	if (int_flags)
	{
		serial->ops->control(serial, RT_DEVICE_CTRL_SET_INT, (void*)int_flags);
	}

	return RT_EOK;
}

static rt_err_t rt_serial_close(struct rt_device *dev)
{
	struct rt_serial_device *serial;
	rt_uint32_t int_flags = 0;

	RT_ASSERT(dev != RT_NULL);
	serial = (struct rt_serial_device*) dev;

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
		int_flags = RT_SERIAL_RX_INT;
	if (dev->flag & RT_DEVICE_FLAG_INT_TX)
		int_flags |= RT_SERIAL_TX_INT;

	if (int_flags)
	{
		serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void*)int_flags);
	}

	return RT_EOK;
}

static rt_size_t rt_serial_read(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size)
{
	rt_uint8_t *ptr;
	rt_uint32_t read_nbytes;
	struct rt_serial_device *serial;

	RT_ASSERT(dev != RT_NULL);
	serial = (struct rt_serial_device*) dev;

	ptr = (rt_uint8_t*)buffer;

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
	{
		/* interrupt mode Rx */
		while (size)
		{
			int ch;

			ch = serial_ringbuffer_getc(serial->int_rx);
			if (ch == -1) break;

			*ptr = ch & 0xff;
			ptr ++;
			size --;
		}
	}
	else
	{
		/* polling mode */
		while ((rt_uint32_t)ptr - (rt_uint32_t)buffer < size)
		{
			*ptr = serial->ops->getc(serial);
            ptr ++;
		}
	}

	read_nbytes = (rt_uint32_t)ptr - (rt_uint32_t)buffer;
	/* set error code */
	if (read_nbytes == 0)
	{
		rt_set_errno(-RT_EEMPTY);
	}

	return read_nbytes;
}

static rt_size_t rt_serial_write(struct rt_device *dev, rt_off_t pos,
                                 const void *buffer, rt_size_t size)
{
	rt_uint8_t *ptr;
	rt_size_t write_nbytes = 0;
	struct rt_serial_device *serial;

	RT_ASSERT(dev != RT_NULL);
	serial = (struct rt_serial_device*) dev;

	ptr = (rt_uint8_t*)buffer;

	if (dev->flag & RT_DEVICE_FLAG_INT_TX)
	{
		/* warning: data will be discarded if buffer is full */
		while (size)
		{
			if (serial_ringbuffer_putchar(serial->int_tx, *ptr) != -1)
			{
				ptr ++;
				size --;
			}
			else break;
		}
	}
	else
	{
		/* polling mode */
		while (size)
		{
			/*
			 * to be polite with serial console add a line feed
			 * to the carriage return character
			 */
			if (*ptr == '\n' && (dev->flag & RT_DEVICE_FLAG_STREAM))
			{
				serial->ops->putc(serial, '\r');
			}
         
			serial->ops->putc(serial, *ptr);

			++ptr;
			--size;
		}
	}

	write_nbytes = (rt_uint32_t)ptr - (rt_uint32_t)buffer;
	if (write_nbytes == 0)
	{
		rt_set_errno(-RT_EFULL);
	}

	return write_nbytes;
}

static rt_err_t rt_serial_control(struct rt_device *dev, rt_uint8_t cmd, void *args)
{
	struct rt_serial_device *serial;

	RT_ASSERT(serial != RT_NULL);
	serial = (struct rt_serial_device*) dev;

	switch (cmd)
	{
	case RT_DEVICE_CTRL_SUSPEND:
		/* suspend device */
		dev->flag |= RT_DEVICE_FLAG_SUSPENDED;
		break;

	case RT_DEVICE_CTRL_RESUME:
		/* resume device */
		dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED;
		break;

	case RT_DEVICE_CTRL_CONFIG:
	    /* configure device */
		serial->ops->configure(serial, (struct serial_configure *)args);
		break;
	}

	return RT_EOK;
}

/*
 * serial register
 */
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial, const char *name, rt_uint32_t flag, void *data)
{
	struct rt_device *device;
	RT_ASSERT(serial != RT_NULL);

	device = &(serial->parent);

	device->type 		= RT_Device_Class_Char;
	device->rx_indicate = RT_NULL;
	device->tx_complete = RT_NULL;

	device->init 		= rt_serial_init;
	device->open		= rt_serial_open;
	device->close		= rt_serial_close;
	device->read 		= rt_serial_read;
	device->write 		= rt_serial_write;
	device->control 	= rt_serial_control;
	device->user_data   = data;

	/* register a character device */
	return rt_device_register(device, name, flag);
}

/* ISR for serial interrupt */
void rt_hw_serial_isr(struct rt_serial_device *serial)
{
	int ch = -1;

	/* interrupt mode receive */
	RT_ASSERT(serial->parent.flag & RT_DEVICE_FLAG_INT_RX);

	while (1)
	{
		ch = serial->ops->getc(serial);
		if (ch == -1) break;

		serial_ringbuffer_putc(serial->int_rx, ch);
	}

	/* invoke callback */
	if (serial->parent.rx_indicate != RT_NULL)
	{
		rt_size_t rx_length;

		/* get rx length */
		rx_length = serial_ringbuffer_size(serial->int_rx);
		serial->parent.rx_indicate(&serial->parent, rx_length);
	}
}
